 /*******************************************************************************
  * Copyright (c) 2003, 2006 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM - Initial API and implementation
  *******************************************************************************/
 package org.eclipse.core.runtime.jobs;

 import java.util.ArrayList ;

 /**
  * A MultiRule is a compound scheduling rule that represents a fixed group of child
  * scheduling rules. A MultiRule conflicts with another rule if any of its children conflict
  * with that rule. More formally, a compound rule represents a logical intersection
  * of its child rules with respect to the <code>isConflicting</code> equivalence
  * relation.
  * <p>
  * A MultiRule will never contain other MultiRules as children. If a MultiRule is provided
  * as a child, its children will be added instead.
  * </p>
  * <p>
  * This class is not intended to be subclassed by clients.
  * </p>
  * @since 3.0
  */
 public class MultiRule implements ISchedulingRule {
     private ISchedulingRule[] rules;

     /**
      * Returns a scheduling rule that encompasses all provided rules. The resulting
      * rule may or may not be an instance of <code>MultiRule</code>. If all
      * provided rules are <code>null</code> then the result will be
      * <code>null</code>.
      *
      * @param ruleArray An array of scheduling rules, some of which may be <code>null</code>
      * @return a combined scheduling rule, or <code>null</code>
      * @since 3.1
      */
     public static ISchedulingRule combine(ISchedulingRule[] ruleArray) {
         ISchedulingRule result = null;
         for (int i = 0; i < ruleArray.length; i++) {
             if (ruleArray[i] == null)
                 continue;
             if (result == null) {
                 result = ruleArray[i];
                 continue;
             }
             result = combine(result, ruleArray[i]);
         }
         return result;
     }

     /**
      * Returns a scheduling rule that encompasses both provided rules. The resulting
      * rule may or may not be an instance of <code>MultiRule</code>. If both
      * provided rules are <code>null</code> then the result will be
      * <code>null</code>.
      *
      * @param rule1 a scheduling rule, or <code>null</code>
      * @param rule2 another scheduling rule, or <code>null</code>
      * @return a combined scheduling rule, or <code>null</code>
      */
     public static ISchedulingRule combine(ISchedulingRule rule1, ISchedulingRule rule2) {
         if (rule1 == rule2)
             return rule1;
         if (rule1 == null)
             return rule2;
         if (rule2 == null)
             return rule1;
         if (rule1.contains(rule2))
             return rule1;
         if (rule2.contains(rule1))
             return rule2;
         MultiRule result = new MultiRule();
         result.rules = new ISchedulingRule[] {rule1, rule2};
         //make sure we don't end up with nested multi-rules
 if (rule1 instanceof MultiRule || rule2 instanceof MultiRule)
             result.rules = flatten(result.rules);
         return result;
     }

     /*
      * Collapses an array of rules that may contain MultiRules into an
      * array in which no rules are MultiRules.
      */
     private static ISchedulingRule[] flatten(ISchedulingRule[] nestedRules) {
         ArrayList myRules = new ArrayList (nestedRules.length);
         for (int i = 0; i < nestedRules.length; i++) {
             if (nestedRules[i] instanceof MultiRule) {
                 ISchedulingRule[] children = ((MultiRule) nestedRules[i]).getChildren();
                 for (int j = 0; j < children.length; j++)
                     myRules.add(children[j]);
             } else {
                 myRules.add(nestedRules[i]);
             }
         }
         return (ISchedulingRule[]) myRules.toArray(new ISchedulingRule[myRules.size()]);
     }

     /**
      * Creates a new scheduling rule that composes a set of nested rules.
      *
      * @param nestedRules the nested rules for this compound rule.
      */
     public MultiRule(ISchedulingRule[] nestedRules) {
         this.rules = flatten(nestedRules);
     }

     /**
      * Creates a new scheduling rule with no nested rules. For
      * internal use only.
      */
     private MultiRule() {
         //to be invoked only by factory methods
 }

     /**
      * Returns the child rules within this rule.
      * @return the child rules
      */
     public ISchedulingRule[] getChildren() {
         return (ISchedulingRule[]) rules.clone();
     }

     /* (non-Javadoc)
      * @see org.eclipse.core.runtime.jobs.ISchedulingRule#contains(org.eclipse.core.runtime.jobs.ISchedulingRule)
      */
     public boolean contains(ISchedulingRule rule) {
         if (this == rule)
             return true;
         if (rule instanceof MultiRule) {
             ISchedulingRule[] otherRules = ((MultiRule) rule).getChildren();
             //for each child of the target, there must be some child in this rule that contains it.
 for (int other = 0; other < otherRules.length; other++) {
                 boolean found = false;
                 for (int mine = 0; !found && mine < rules.length; mine++)
                     found = rules[mine].contains(otherRules[other]);
                 if (!found)
                     return false;
             }
             return true;
         }
         for (int i = 0; i < rules.length; i++)
             if (rules[i].contains(rule))
                 return true;
         return false;
     }

     /* (non-Javadoc)
      * @see org.eclipse.core.runtime.jobs.ISchedulingRule#isConflicting(org.eclipse.core.runtime.jobs.ISchedulingRule)
      */
     public boolean isConflicting(ISchedulingRule rule) {
         if (this == rule)
             return true;
         if (rule instanceof MultiRule) {
             ISchedulingRule[] otherRules = ((MultiRule) rule).getChildren();
             for (int j = 0; j < otherRules.length; j++)
                 for (int i = 0; i < rules.length; i++)
                     if (rules[i].isConflicting(otherRules[j]))
                         return true;
         } else {
             for (int i = 0; i < rules.length; i++)
                 if (rules[i].isConflicting(rule))
                     return true;
         }
         return false;
     }

     /*
      * For debugging purposes only.
      */
     public String toString() {
         StringBuffer buffer = new StringBuffer ();
         buffer.append("MultiRule["); //$NON-NLS-1$
 int last = rules.length - 1;
         for (int i = 0; i < rules.length; i++) {
             buffer.append(rules[i]);
             if (i != last)
                 buffer.append(',');
         }
         buffer.append(']');
         return buffer.toString();
     }
 }

